/*
 * Copyright 2014 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.drools.workbench.models.testscenarios.backend;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.drools.core.impl.StatefulKnowledgeSessionImpl;
import org.drools.workbench.models.testscenarios.shared.Scenario;
import org.junit.internal.AssumptionViolatedException;
import org.junit.internal.runners.model.EachTestNotifier;
import org.junit.runner.Description;
import org.junit.runner.Runner;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.model.InitializationError;
import org.kie.api.runtime.KieSession;
import org.kie.api.runtime.rule.FactHandle;

public class ScenarioRunner4JUnit extends Runner {

    private final int maxRuleFirings;
    // The description of the test suite
    private Description descr;
    // the actual scenario test to be executed
    private List<Scenario> scenarios;
    private Map<String, KieSession> ksessions;

    public ScenarioRunner4JUnit( final Scenario scenario,
                                 final Map<String, KieSession> ksessions,
                                 final int maxRuleFirings ) throws InitializationError {
        this.scenarios = new ArrayList<Scenario>();
        this.scenarios.add( scenario );
        this.ksessions = ksessions;
        this.descr = Description.createSuiteDescription( "Scenario test cases" );
        this.maxRuleFirings = maxRuleFirings;
    }

    public ScenarioRunner4JUnit( final Scenario scenario,
                                 final Map<String, KieSession> ksessions ) throws InitializationError {
        this( scenario,
              ksessions,
              0 );
    }

    public ScenarioRunner4JUnit( final List<Scenario> scenarios,
                                 final Map<String, KieSession> ksessions ) throws InitializationError {
        this( scenarios,
              ksessions,
              0 );
    }

    public ScenarioRunner4JUnit( final List<Scenario> scenarios,
                                 final Map<String, KieSession> ksessions,
                                 final int maxRuleFirings ) throws InitializationError {
        this.scenarios = scenarios;
        this.ksessions = ksessions;
        this.descr = Description.createSuiteDescription( "Scenario test cases" );
        this.maxRuleFirings = maxRuleFirings;
    }

    @Override
    public Description getDescription() {
        return descr;
    }

    @Override
    public void run( RunNotifier notifier ) {
        for ( Scenario scenario : scenarios ) {
            runScenario( notifier, scenario );
        }
    }

    private void runScenario( RunNotifier notifier,
                              Scenario scenario ) {
        Description childDescription = Description.createTestDescription( getClass(),
                                                                          scenario.getName() );
        descr.addChild( childDescription );
        EachTestNotifier eachNotifier = new EachTestNotifier( notifier,
                                                              childDescription );
        try {
            eachNotifier.fireTestStarted();

            //If a KieSession is not available, fail fast
            if ( ksessions == null || ksessions.values().isEmpty() ) {
                eachNotifier.addFailure( new NullKieSessionException( "Unable to get a Session to run tests. Check the project for build errors." ) );
            } else {

                KieSession ksession = getKSession( scenario.getKSessions() );

                if ( ksession == null ) {
                    String ksessionName = getKSessionName( scenario.getKSessions() );
                    if ( ksessionName == null ) {
                        eachNotifier.addFailure( new NullPointerException( "Test scenario runner could not find the default knowledge session." ) );
                    } else {
                        eachNotifier.addFailure( new NullPointerException( "Test Scenarios require Stateful KieSession to run." ) );
                    }
                } else {
                    final ScenarioRunner runner = new ScenarioRunner( ksession,
                                                                      maxRuleFirings );
                    runner.run( scenario );
                    if ( !scenario.wasSuccessful() ) {
                        StringBuilder builder = new StringBuilder();
                        for ( String message : scenario.getFailureMessages() ) {
                            builder.append( message ).append( "\n" );
                        }
                        eachNotifier.addFailedAssumption( new AssumptionViolatedException( builder.toString() ) );
                    }

                    // FLUSSSSSH!
                    for ( FactHandle factHandle : ksession.getFactHandles() ) {
                        ksession.delete( factHandle );
                    }

                    resetKieSession( ksession );
                }
            }
        } catch ( Throwable t ) {
            eachNotifier.addFailure( t );
        } finally {
            // has to always be called as per junit docs
            eachNotifier.fireTestFinished();
        }
    }

    private void resetKieSession( final KieSession kieSession ) {
        final StatefulKnowledgeSessionImpl statefulKnowledgeSession = (StatefulKnowledgeSessionImpl) kieSession;

        statefulKnowledgeSession.reset();
    }

    private KieSession getKSession( List<String> ksessionNames ) {
        String ksessionName = getKSessionName( ksessionNames );
        if ( ksessions.containsKey( ksessionName ) ) {
            return ksessions.get( ksessionName );
        } else {
            return null;
        }
    }

    private String getKSessionName( List<String> ksessionNames ) {
        if ( ksessionNames != null && !ksessionNames.isEmpty() ) {
            return ksessionNames.iterator().next();
        } else {
            return null;
        }

    }

}
